/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Copyright 2002-2006 by Mark Wolgemuth, All rights reserved.             */
/* Copyright 2002-2006 by Employease, Inc.  All rights reserved.           */
/*                                                                         */
/* Redistribution and use in source and binary forms, with or without      */
/* modification, are permitted provided that the following conditions      */
/* are met:                                                                */
/*                                                                         */
/*    1. Redistributions of source code must retain the above copyright    */
/* notice, this list of conditions and the following disclaimer.           */
/*    2. Redistributions in binary form must reproduce the above copyright */
/* notice, this list of conditions and the following disclaimer in the     */
/* documentation and/or other materials provided with the distribution.    */
/*                                                                         */
/* THIS SOFTWARE IS PROVIDED BY THE MOD_ATHENA PROJECT ``AS IS'' AND ANY   */
/* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE       */
/* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR      */
/* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR        */
/* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,   */
/* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,     */
/* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR      */
/* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY     */
/* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT            */
/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE   */
/* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.    */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "ath.h"
#include "apr_sha1.h"

static const char *_secret = NULL;

/* the hash table stores mappings from the sha1 hash given to client */
/* to the original server generated readable smartfarm cookie string */

static ath_smartfarm_type _ath_smartfarm_parser(request_rec *r,
						char *strSmartfarm, 
						char **strFarm);
static ath_smartfarm_type _ath_smartfarm_prepend(apr_pool_t *p, 
						 char *arg, char **strFarm);
static ath_smartfarm_type _ath_smartfarm_postpend(apr_pool_t *p, 
						  char *arg, char **strFarm);
static ath_smartfarm_type _ath_smartfarm_replace(apr_pool_t *p, 
						 char *arg, char **strFarm);
static ath_smartfarm_type _ath_smartfarm_sticky(apr_pool_t *p, 
						char *arg, char **strFarm);
static ath_smartfarm_type _ath_smartfarm_blind(apr_pool_t *p, 
					       char *arg, char **strFarm);
static ath_smartfarm_type _ath_smartfarm_match(apr_pool_t *p, char *arg1, 
					       char *arg2, char **strFarm);
static ath_smartfarm_type _ath_smartfarm_regex(apr_pool_t *p, 
					       char *arg, char **strFarm);
static apr_status_t _ath_smartfarm_validate(request_rec *r,
					    char **strSmartfarm);

void
ath_smartfarm_init(apr_pool_t *pool, const char *data)
{
    ASSERT(pool);
 
    /* only init these if AthSmartfarmProtect was set */
    if (data)
      {
	  _secret = data;
      }
}

void
ath_smartfarm_handler(request_rec *r, char **str_farm, 
		      const char *prox_proto,
		      const char *prox_portfile)
{
    char *strSmartfarm = NULL;
    ath_conf_ap *aconf = ATH_CONF_AP_GET(r->server);
    ath_engine *engine = ath_engine_get();

    strSmartfarm = ath_cookie_getval(r, aconf->cookie_name_smartfarm);
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, 
		 "strSmartfarm: cookie[%s] *str_farm: [%s]", strSmartfarm, *str_farm);

    if (strSmartfarm)
      {
	  ath_smartfarm_type sftype;

	  engine->stats->hits_smartfarm++;

	  /* validate if hash protected */
	  if (_secret)
	    {
		apr_status_t rv;
		rv = _ath_smartfarm_validate(r, &strSmartfarm);
		if (rv != APR_SUCCESS)
		  {
		      engine->stats->hits_smartfarm_tamper++;
		      /* short circuit smartfarm-- will fall through */
		      /* to regular handling ignoring the cookie */
		      if (aconf->smartfarm_tamper_url)
			{
			    ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, 
					  "WARNING: tampered or corrupt "
					  "cookie: [%s] forwarding to: [%s]",
					  strSmartfarm, aconf->smartfarm_tamper_url);
			    r->filename = apr_pstrcat(r->pool, "proxy:", 
						      aconf->smartfarm_tamper_url, 
						      NULL);
			    *str_farm = NULL;
			}
		      else
			{
			    ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, 
					  "WARNING: tampered or corrupt "
					  "cookie: [%s] falling back on: [%s]",
					  strSmartfarm, *str_farm);
			}
		      return;
		  }
	    }
	  
	  sftype = _ath_smartfarm_parser(r, strSmartfarm, str_farm);

	  /* if it is blind sticky, then always use what cookie */
	  /* says and treat like simple proxy */
	  if (sftype == ATH_SFARM_BLIND)
	    {
		r->filename =
		  apr_pstrcat(r->pool,
			      /* str_farm now contains desired target */
			      prox_proto, *str_farm,
			      prox_portfile, NULL);
		/* signals no further handling in request.c */
		*str_farm = NULL;
	    }
	  /* if it is sticky (not blind) then check phys status */
	  /* fail to allow if not found, otherwise honor status */
	  else if (sftype == ATH_SFARM_STICKY)
	    {
		ath_phys *phys;

		/* str_farm now contains desired target */
		phys = ath_phys_for_host(*str_farm);
		if ((phys) && (phys->host[0] != (int) NULL))
		  {
		      if ((phys->on != ATH_ON)
			  && (engine->offline_url[0] != (int) NULL))
			{
			    r->filename = apr_pstrcat(r->pool, "proxy:", 
						      engine->offline_url, NULL);
			    if (aconf->engine_offline_url_force_get) {
				r->method = "GET";
				r->method_number = M_GET;
			    }
			    engine->stats->hits_offlineurl++;

			}
		      else if (((phys->up != ATH_UP)
				|| (phys->in != ATH_IN))
			       && (engine->down_url[0] != (int) NULL))
			{
			    r->filename = apr_pstrcat(r->pool, "proxy:", 
						      engine->down_url, NULL);
			    if (aconf->engine_down_url_force_get) {
				r->method = "GET";
				r->method_number = M_GET;
			    }
			    engine->stats->hits_downurl++;
			}
		      else
			{
			    r->filename = 
			      apr_pstrcat(r->pool, prox_proto, 
					  phys->host, prox_portfile, 
					  NULL);
			    engine->stats->hits_idx_phys[phys->idx]++;
			}
		  }
		/* fail to returning unmodified cookie value */
		else
		  {
		      r->filename = 
			apr_pstrcat(r->pool, prox_proto, 
				    *str_farm, prox_portfile, 
				    NULL);				  
		  }
		/* signals no further handling in request.c */
		*str_farm = NULL;	
	    }
	  /* else str_farm is returned for further handling */
      }
}

static ath_smartfarm_type
_ath_smartfarm_parser(request_rec *r,
		      char *strSmartfarm, char **strFarm)
{
    char *inter, *str_tok;
    const char *str_sep = ATH_SFARM_STR_DELIM;
    ath_smartfarm_type sftype = ATH_SFARM_UNDEF;

    ASSERT(strSmartfarm);
    ASSERT(*strFarm);

    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, 
		  "rule: %s given: %s", strSmartfarm, *strFarm);


    str_tok = apr_strtok(strSmartfarm, str_sep, &inter);
    str_remains = inter; // this position is the remainder of the string

    while (str_tok != NULL)
      {
	  if (!strncmp(str_tok, ATH_SFARM_STR_PREPEND, 
		       strlen(ATH_SFARM_STR_PREPEND)))
	    {		
		sftype = _ath_smartfarm_prepend(r->pool, str_remains, strFarm);
	    }
	  else if (!strncmp(str_tok, ATH_SFARM_STR_POSTPEND, 
			    strlen(ATH_SFARM_STR_POSTPEND)))
	    {
		sftype = _ath_smartfarm_postpend(r->pool, str_remains, strFarm);
	    }
	  else if (!strncmp(str_tok, ATH_SFARM_STR_REPLACE, 
			    strlen(ATH_SFARM_STR_REPLACE)))
	    {
		sftype = _ath_smartfarm_replace(r->pool, str_remains, strFarm);
	    }
	  else if (!strncmp(str_tok, ATH_SFARM_STR_STICKY, 
			    strlen(ATH_SFARM_STR_STICKY)))
	    {
		sftype = _ath_smartfarm_sticky(r->pool, str_remains, strFarm);
	    }
	  else if (!strncmp(str_tok, ATH_SFARM_STR_BLIND, 
			    strlen(ATH_SFARM_STR_BLIND)))
	    {
		sftype = _ath_smartfarm_blind(r->pool, str_remains, strFarm);
	    }
	  else if (!strncmp(str_tok, ATH_SFARM_STR_MATCH, 
			    strlen(ATH_SFARM_STR_MATCH)))
	    {
		char *arg1 = NULL;
		char *arg2 = NULL;
		arg1 = apr_strtok(NULL, str_sep, &inter);
		if (arg1)
		    arg2 = apr_strtok(NULL, str_sep, &inter);
		sftype = _ath_smartfarm_match(r->pool, arg1, arg2, strFarm);
	    }
	  else if (!strncmp(str_tok, ATH_SFARM_STR_REGEX, 
			    strlen(ATH_SFARM_STR_REGEX)))
	    {
		sftype = _ath_smartfarm_regex(r->pool, str_remains, strFarm);
	    }
	  str_tok = apr_strtok(NULL, str_sep, &inter);
      }

    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, 
		  "rule: %s processed: %s", strSmartfarm, *strFarm);

    return sftype;
}

static ath_smartfarm_type 
_ath_smartfarm_prepend(apr_pool_t *pool, char *arg, char **strFarm)
{
    char *strNewFarm;

    if ((*strFarm) && (arg))
      {
	  strNewFarm = apr_pstrcat(pool, arg, *strFarm, NULL);
	  *strFarm = strNewFarm;
	  return ATH_SFARM_PREPEND;
      }
    return ATH_SFARM_BROKEN;
}

static ath_smartfarm_type 
_ath_smartfarm_postpend(apr_pool_t *pool, char *arg, char **strFarm)
{
    char *strNewFarm;

    if ((*strFarm) && (arg))
      {
	  strNewFarm = apr_pstrcat(pool, *strFarm, arg, NULL);
	  *strFarm = strNewFarm;
	  return ATH_SFARM_POSTPEND;
      }
    return ATH_SFARM_BROKEN;
}

static ath_smartfarm_type 
_ath_smartfarm_replace(apr_pool_t *pool, 
		       char *arg, char **strFarm)
{
    if ((*strFarm) && (arg))
      {
	  /* it's safe not to dupe the arg because it's not const */
	  /* and not used anywhere else */
	  *strFarm = arg;
	  return ATH_SFARM_REPLACE;
      }
    return ATH_SFARM_BROKEN;
}

static ath_smartfarm_type 
_ath_smartfarm_sticky(apr_pool_t *pool, 
		      char *arg, char **strFarm)
{
    if ((*strFarm) && (arg))
      {
	  /* it's safe not to dupe the arg because it's not const */
	  /* and not used anywhere else */
	  *strFarm = arg;
	  return ATH_SFARM_STICKY;
      }
    return ATH_SFARM_BROKEN;
}

static ath_smartfarm_type 
_ath_smartfarm_blind(apr_pool_t *pool, 
		     char *arg, char **strFarm)
{
    if ((*strFarm) && (arg))
      {
	  /* it's safe not to dupe the arg because it's not const */
	  /* and not used anywhere else */
	  *strFarm = arg;
	  return ATH_SFARM_BLIND;
      }
    return ATH_SFARM_BROKEN;
}

/* match a string, then replace it with arg2 */
static ath_smartfarm_type 
_ath_smartfarm_match(apr_pool_t *pool, 
		     char *arg1, char *arg2, char **strFarm)
{
    char *strNewFarm;
    
    /* allows arg2 to be null, will force bypassing the farm lookup */
    if ((*strFarm) && (arg1))
      {
	  if (((strcmp(*strFarm, arg1)))
	      || ((*arg1 == '!') 
		  && ((++arg1 != NULL) 
		      && (strcmp(*strFarm, ++arg1)))))
	    {
		/* it's safe not to dupe the arg because it's not const */
		/* and not used anywhere else */
		*strFarm = arg2;
		return ATH_SFARM_MATCH;
	    }
      }
    return ATH_SFARM_BROKEN;
}

/* 2006 04 25 not implemented yet */
static ath_smartfarm_type 
_ath_smartfarm_regex(apr_pool_t *pool, 
		     char *arg, char **strFarm)
{
    return ATH_SFARM_BROKEN;
}

static apr_status_t
_ath_smartfarm_validate(request_rec *r, char **strSmartfarm)
{
    char *strConcat, *strRealSfarm, *strHashClient, *inter;
    char *strSep = ATH_SFARM_STR_DELIM_HASH;

    ASSERT(r);
    ASSERT(*strSmartfarm);
    ASSERT(_secret);

    strConcat = *strSmartfarm;

    strRealSfarm = apr_strtok(strConcat, strSep, &inter);
    if (strRealSfarm)
	strHashClient = apr_strtok(NULL, strSep, &inter);
    else
	return APR_EINVAL;
    
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
		 "smartfarm: %s client hash: %s",
		 strRealSfarm, strHashClient);

    if (strHashClient)
      {
	  char *input;
	  char *strHashVerify;

	  input = apr_pstrcat(r->pool, strRealSfarm, _secret, NULL);
	  strHashVerify = apr_pcalloc(r->pool, sizeof(char[APR_SHA1_DIGESTSIZE]));

	  apr_sha1_base64(input, strlen(input), strHashVerify);

	  ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
		       "expect hash: %s", strHashVerify);

	  ASSERT(strHashVerify);
	  if (strcmp(strHashClient, strHashVerify))
	      return APR_EINVAL;
	  else
	    {
		*strSmartfarm = strRealSfarm;
		return APR_SUCCESS;
	    }
      }
    else
	return APR_EINVAL;
}
